<?php
/* 
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 * Description of RqlAutomato
 *
 * @author Admin
 */
class RqlAutomato {

    private $states = array();
    private $state = null;

    private $income = '';
    private $position = 0;
    public  $outcome = array();
    private $stopped_at = '';
    
    /**
     * Функция добавляет обработчик входных данных для определённого состояния
     * Обработчик должен возвращать новое состояние автомата или null
     * @param integer $state код состояния, в котором
     * @param regexp $income регексп, при совпадении которого со входным символом будет вызван обработчик
     * @param mixed $handler функция или метод-обработчик
     */
    public function add_handler($state, $income, $handler) {
        $this->states[$state][$income] = $handler;
    }

    public function clear_handler($state, $income) {
        unset($this->states[$state][$income]);
    }

    public function  __construct($income, $end_symbols = null) {
        $this->state = new RqlAutomatoState(RqlAutomatoState::START | RqlAutomatoState::WAITING_NODE);
        $this->income = $income;


        $this->add_handler(RqlAutomatoState::PATH | RqlAutomatoState::WAITING_NODE, '[+\-$]+', array($this, 'node_operation_handler'));
        $this->add_handler(RqlAutomatoState::PATH | RqlAutomatoState::WAITING_NODE, '\w+', array($this, 'node_handler'));
        $this->add_handler(RqlAutomatoState::PATH | RqlAutomatoState::WAITING_OPERATED_NODE, '\w+', array($this, 'operated_node_handler'));
//        $this->add_handler(RqlAutomatoState::BRANCHING | RqlAutomatoState::WAITING_NODE, '[a-zA-Z]+', array($this, 'node_handler'));
        $this->add_handler(RqlAutomatoState::PATH | RqlAutomatoState::WAITING_DESDENDANT , '\/', array($this, 'descendant_handler'));
        $this->add_handler(RqlAutomatoState::PATH | RqlAutomatoState::WAITING_DESDENDANT , 'as\s+\w+', array($this, 'alias_handler'));
        $this->add_handler(RqlAutomatoState::PATH | RqlAutomatoState::WAITING_NODE , '\{', array($this, 'fork_handler'));
        $this->add_handler(RqlAutomatoState::PATH | RqlAutomatoState::WAITING_DESDENDANT , '\[', array($this, 'description_handler'));
        $this->add_handler(RqlAutomatoState::PATH | RqlAutomatoState::WAITING_DESDENDANT , '::', array($this, 'stop_handler'));

        $this->add_handler(RqlAutomatoState::ANCHOR | RqlAutomatoState::WAITING_DESCRIPTION, 'set', array($this, 'anchor_set_handler'));
        $this->add_handler(RqlAutomatoState::ANCHOR | RqlAutomatoState::WAITING_DESCRIPTION, ']', array($this, 'anchor_end_handler'));
        $this->add_handler(RqlAutomatoState::ANCHOR | RqlAutomatoState::WAITING_UPDATE, ']', array($this, 'anchor_end_handler'));
        $this->add_handler(RqlAutomatoState::ANCHOR | RqlAutomatoState::WAITING_DESCRIPTION, '.', array($this, 'anchor_symbol_handler'));
        $this->add_handler(RqlAutomatoState::ANCHOR | RqlAutomatoState::WAITING_UPDATE, '.', array($this, 'update_symbol_handler'));

        if ($end_symbols == null) {
            $this->income .= "\r\n";
            $this->add_handler(RqlAutomatoState::PATH | RqlAutomatoState::WAITING_DESDENDANT, '\r\n', array($this, 'stop_handler'));
        } else {
            foreach($end_symbols as $end) {
                $this->add_handler(RqlAutomatoState::PATH | RqlAutomatoState::WAITING_DESDENDANT, $end, array($this, 'stop_handler'));
            }
        }
    }

    public function start() {

        while($this->state->state != RqlAutomatoState::DONE) {
            if (!key_exists($this->state->state, $this->states)) throw new Exception('No handles defined for state '.$this->state->state);
            $state_handlers = $this->states[$this->state->state];
            $str = substr($this->income, $this->state->position);
            foreach($state_handlers as $key => $handler) {
                if (($key && preg_match('/^'.$key.'/i', $str, $matched)) || $str == $key) {
                    $state = call_user_func_array($handler, array($this->state, $matched));
                    if (!is_a($state, 'RqlAutomatoState')) {
                        throw new Exception('state handler must return RqlAutomatoState!');
                    }
                    $this->state = $state;
                    break;
                }
            }
            if ($this->state->state == RqlAutomatoState::DONE) return;
            $this->state->next();

        }
    }

    /**
     *
     * @return RqlAutomatoState
     */
    public function get_state() {
        return $this->state;
    }

    public function node_handler(RqlAutomatoState $state, $matched) {
        $out = new RqlNode($matched[0]);
        $state->add_outcome($out);
        $state->replace_state(RqlAutomatoState::WAITING_NODE, RqlAutomatoState::WAITING_DESDENDANT);
        $state->skip(strlen($matched[0])-1);
        return $state;
    }
    public function operated_node_handler(RqlAutomatoState $state, $matched) {
        $out = $state->get_last_outcome();
        $out->name = $matched[0];
        $state->set_last_outcome($out);
        $state->replace_state(RqlAutomatoState::WAITING_OPERATED_NODE, RqlAutomatoState::WAITING_DESDENDANT);
        $state->skip(strlen($matched[0])-1);
        return $state;
    }

    /**
     * Обра
     */
    public function descendant_handler(RqlAutomatoState $state, $matched) {
        // ничего не делаем, просто отдыхаем :)
        $state->replace_state(RqlAutomatoState::WAITING_DESDENDANT, RqlAutomatoState::WAITING_NODE);
        return $state;
    }

    public function stop_handler(RqlAutomatoState $state, $matched) {
        $state->skip(strlen($matched[0])-1);
        $state->state = RqlAutomatoState::DONE;
        $this->stopped_at = $matched[0];
        return $state;
    }

    public function fork_handler(RqlAutomatoState $state, $matched) {
        // задача - запускать новый автомат с указанием стоп-символов
        // до тех пор, пока не сработает стоп-символ } или \r\n
        $operation = false;
        $branches = array();
        while(true) {
            $str = substr($this->income, $state->position+1);
            $automato = new RqlAutomato($str, array('&&', '\\|\\|', '}', "\r\n"));
            $automato -> start();
            $stopper = $automato->stopped_at;
            $sub_state = $automato->get_state();
            $this->state->skip($sub_state->position);
            $branches[] = $sub_state->outcome;
            if ($stopper == "\r\n") {
                throw new Exception("Unexpected end of path, maybe unclosed fork?");
            }
            if ($stopper == '}') {
                if ($operation === false) {
                    throw new Exception('Unexpected end of fork, maybe only one branch defined?');
                }
                break;
            }
            if ($operation === false) {
                $operation = $stopper;
            } elseif ($stopper != $operation) {
                throw new Exception('Mixing || with && is not allowed at same fork.');
            }

        }
        $out = new RqlFork($operation, $branches);
        $state->add_outcome($out);
        $state->state = RqlAutomatoState::PATH | RqlAutomatoState::WAITING_DESDENDANT;
        return $state;
    }

    public function alias_handler(RqlAutomatoState $state, $matched) {
        $str = trim(array_pop(explode(' ',$matched[0])));
        if ($state->get_last_outcome_type() != 'RqlNode') {
            throw new Exception('Using aliases allowed only for nodes!');
        }
        $last = $state->get_last_outcome();
        $last->alias = $str;
        $state->set_last_outcome($last);
        $state->skip(strlen($matched[0])-1);
        return $state;
    }

    public function description_handler(RqlAutomatoState $state, $matched) {
        if ($state->get_last_outcome_type() != 'RqlNode') {
            throw new Exception('Descriptions allowed only for nodes!');
        }
        $state->state = RqlAutomatoState::ANCHOR | RqlAutomatoState::WAITING_DESCRIPTION;
        return $state;
    }


    public function anchor_symbol_handler(RqlAutomatoState $state, $matched) {
        $symbol  = $matched[0];

        $last = $state->get_last_outcome();
        $last->description .= $symbol;
        $state->set_last_outcome($last);

        return $state;
    }

    public function anchor_set_handler(RqlAutomatoState $state, $matched) {
        $state->skip(strlen($matched[0])-1);
        $state->replace_state(RqlAutomatoState::WAITING_DESCRIPTION, RqlAutomatoState::WAITING_UPDATE);
        $last = $state->get_last_outcome();
        if ($last->description) $last->setDescription($last->description);
        $state->set_last_outcome($last);
        return $state;
    }

    public function anchor_end_handler(RqlAutomatoState $state, $matched) {
        $state->state = RqlAutomatoState::PATH | RqlAutomatoState::WAITING_DESDENDANT;
        $last = $state->get_last_outcome();
        if ($last->description) $last->setDescription($last->description);
        $f = $last->modify;
        $last->modify = null;
        if ($f) $last->setModify($f);
        $state->set_last_outcome($last);
        return $state;
    }

    public function update_symbol_handler(RqlAutomatoState $state, $matched) {
        $symbol  = $matched[0];

        $last = $state->get_last_outcome();
        $last->modify .= $symbol;
        $state->set_last_outcome($last);

        return $state;
    }

    /**
     *
     * @return RqlPath
     */
    public function result() {
        return $this->state->outcome;
    }

    public function node_operation_handler(RqlAutomatoState $state, $matched) {
        $symbol = $matched[0];
        $last = new RqlNode('');
        $last->operation = $symbol;
        $state->add_outcome($last);
        $state->replace_state(RqlAutomatoState::WAITING_NODE, RqlAutomatoState::WAITING_OPERATED_NODE);
        return $state;
    }
}

?>